package com.caishi.lkx.order.pay;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.user.config.ThirdOauthConfig;
import com.caishi.lkx.user.model.GroupModel;
import com.caishi.lkx.user.service.IGroupService;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
import com.zzw.common.exception.BizException;
import com.zzw.common.exception.BizRuntimeException;

import com.caishi.lkx.order.OrderResultCode;
import com.caishi.lkx.order.config.WeixinPayConfig;
import com.caishi.lkx.order.model.PaymentModel;
import com.caishi.lkx.order.model.PaymentRefundLogModel;
import com.caishi.lkx.order.service.IOrderService;
import com.caishi.lkx.user.UserResultCode;
import com.caishi.lkx.user.login.WxcxLoginServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author by keray
 * date:2019/9/9 11:49
 */
@Configuration(value = "weiXinOnlinePay")
@Slf4j
public class WeiXinPayPlugins implements PayPlugins {

    protected final WeixinPayConfig weixinPayConfig;

    @Resource
    private ThirdOauthConfig thirdOauthConfig;


    @Resource
    private IOrderService orderService;

    @Resource
    private WxcxLoginServiceImpl wxcxLoginService;

    @Resource
    private IGroupService groupService;

    public WeiXinPayPlugins(@Qualifier("weixinPayConfig") WeixinPayConfig weixinPayConfig) {
        this.weixinPayConfig = weixinPayConfig;
    }

    public enum WeixinPayType {
        // app支付
        app,
        // h5支付
        h5,
        // 小程序支付
        xcx,
        // jsapi
        jsapi,
        unknown
    }

    @Override
    public boolean open() {
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> sign(PaymentModel paymentModel, Object val, Map<String, Object> otherParam) {
        try {
            // openId处理
            _openProcess(paymentModel, val, otherParam);
            if (val == WeixinPayType.app) {
                return new HashMap<>(_appPay(paymentModel, otherParam));
            } else if (val == WeixinPayType.h5) {
                return new HashMap<>(_h5Pay(paymentModel, otherParam));
            } else if (val == WeixinPayType.xcx) {
                return new HashMap<>(_xcx(paymentModel, otherParam));
            } else if (val == WeixinPayType.jsapi) {

                return new HashMap<>(_jsapiPay(paymentModel, otherParam));
            } else {
                return new HashMap<>(_defaultPay(paymentModel, otherParam));
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("微信签名失败：", e);
        }
        throw new BizRuntimeException(OrderResultCode.payFail);
    }

    @Override
    public boolean prePayCallBack(PaymentModel paymentModel) {
        try {
            Map<String, String> result = getWxPay(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).orderQuery(
                    MapUtil.<String, String>builder()
                            .put("out_trade_no", paymentModel.getOrderSn())
                            .build()
            );
            if ("SUCCESS".equals(result.get("return_code"))) {
                if ("SUCCESS".equals(result.get("result_code"))) {
                    return "SUCCESS".equals(result.get("trade_state"));
                } else {
                    return false;
                }
            }
            log.error("微信订单查询失败 data={}", result);
        } catch (Exception e) {
            //  查询失败后邮件通知处理  异常还是通过
            log.error("订单查询失败：", e);
        }
        return true;
    }

    @Override
    public boolean refund(PaymentModel paymentModel, PaymentRefundLogModel paymentRefundLogModel) {
        try {
            if (paymentRefundLogModel.getRefundAmount() == 0) {
                return true;
            }
            if (fetchRefund(paymentModel, paymentRefundLogModel)) {
                return true;
            }
            Map<String, String> result = getWxPay(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).refund(MapUtil.<String, String>builder()
                    .put("out_trade_no", paymentModel.getOrderSn())
                    .put("out_refund_no", paymentModel.getOrderSn())
                    .put("total_fee", String.valueOf(paymentModel.getPaymentAmount()))
                    .put("refund_fee", String.valueOf(paymentRefundLogModel.getRefundAmount()))
                    .build());
            log.info("微信退款接口结果：{}", result);
            return "SUCCESS".equals(result.get("result_code")) && "SUCCESS".equals(result.get("return_code"));
        } catch (Exception e) {
            log.error("微信退款失败:", e);
        }
        return false;
    }

    private boolean fetchRefund(PaymentModel paymentModel, PaymentRefundLogModel paymentRefundLogModel) {
        try {
            Map<String, String> result = getWxPay(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).refundQuery(MapUtil.<String, String>builder()
                    .put("out_trade_no", paymentModel.getOrderSn())
                    .put("total_fee", String.valueOf(paymentModel.getPaymentAmount()))
                    .put("refund_fee", String.valueOf(paymentRefundLogModel.getRefundAmount()))
                    .build());
            return "SUCCESS".equals(result.get("result_code")) && "SUCCESS".equals(result.get("return_code"));
        } catch (Exception ignored) {

        }
        return false;
    }

    @Override
    public void orderCancel(PaymentModel paymentModel) {
        try {
            getWxPay(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).closeOrder(
                    MapUtil.<String, String>builder()
                            .put("out_trade_no", paymentModel.getOrderSn())
                            .put("nonce_str", WXPayUtil.generateNonceStr())
                            .build()
            );
        } catch (Exception e) {
            log.error("微信订单取消异常:", e);
        }
    }

    public Map<String, String> _appPay(PaymentModel paymentModel, Map<String, Object> otherParam) throws Exception {
        Long groupId = orderService.getById(paymentModel.getOrderId()).getGroupId();
        Map<String, String> result = getWxPay(val(paymentModel.getMode()),groupId).unifiedOrder(
                MapUtil.builder(baseParam(paymentModel, otherParam))
                        .put("trade_type", "APP")
                        .build());
        WeixinPayConfig config = getWeixinPayConfig(val(paymentModel.getMode()),groupId);
        Map<String, String> returnResult = MapUtil.<String, String>builder()
                .put("appid", config.getAppid())
                .put("partnerid", config.getMchid())
                .put("prepayid", result.get("prepay_id"))
                .put("package", "Sign=WXPay")
                .put("noncestr", WXPayUtil.generateNonceStr())
                .put("timestamp", String.valueOf(System.currentTimeMillis() / 1000))
                .build();

        returnResult.put("sign", WXPayUtil.generateSignature(returnResult, config.getSignKey()));
        return returnResult;
    }

    public Map<String, String> _h5Pay(PaymentModel paymentModel, Map<String, Object> otherParam) throws Exception {
        if (otherParam == null) {
            throw new BizRuntimeException(OrderResultCode.payFail);
        }
        Map<String, Object> hashMap = new HashMap<>();
        HashMap<String, Object> map = new HashMap<>();
        return getWxPay(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).unifiedOrder(
                MapUtil.builder(baseParam(paymentModel, otherParam))
                        .put("trade_type", "MWEB")
                        .build()
        );
    }

    public Map<String, String> _jsapiPay(PaymentModel paymentModel, Map<String, Object> otherParam) throws Exception {
        if (otherParam == null) {
            throw new BizRuntimeException(OrderResultCode.payFail);
        }
        Long groupId = orderService.getById(paymentModel.getOrderId()).getGroupId();
        Map<String, String> result = getWxPay(val(paymentModel.getMode()),groupId).unifiedOrder(
                MapUtil.builder(baseParam(paymentModel, otherParam))
                        .put("appid", getWeixinPayConfig(val(paymentModel.getMode()),groupId).getAppid())
                        .put("trade_type", "JSAPI")
                        .put("openid", String.valueOf(otherParam.get("openid")))
                        .build());
        result.put("openid", String.valueOf(otherParam.get("openid")));
        return _getStringStringMap(result, paymentModel);
    }

    public Map<String, String> _xcx(PaymentModel paymentModel, Map<String, Object> otherParam) throws Exception {
        if (otherParam == null) {
            throw new BizRuntimeException(OrderResultCode.payFail);
        }

        Long groupId = orderService.getById(paymentModel.getOrderId()).getGroupId();
        Map<String, String> param = MapUtil.builder(baseParam(paymentModel, otherParam))
                .put("appid", getWeixinPayConfig(val(paymentModel.getMode()),groupId).getAppid())
                .put("trade_type", "JSAPI")
                .put("openid", String.valueOf(otherParam.get("openid")))
                .build();
        Map<String, String> result = getWxPay(val(paymentModel.getMode()),groupId).unifiedOrder(param);
        result.put("openid", String.valueOf(otherParam.get("openid")));
        return _getStringStringMap(result, paymentModel);
    }

    public Map<String, String> _defaultPay(PaymentModel paymentModel, Map<String, Object> otherParam) throws Exception {
        return getWxPay(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).unifiedOrder(
                MapUtil.builder(baseParam(paymentModel, otherParam))
                        .put("trade_type", "NATIVE")
                        .build()
        );
    }


    private Map<String, String> _getStringStringMap(Map<String, String> result, PaymentModel paymentModel) throws Exception {
        if ("SUCCESS".equals(result.get("return_code")) && "SUCCESS".equals(result.get("result_code"))) {
            Long groupId = orderService.getById(paymentModel.getOrderId()).getGroupId();
            Map<String, String> returnResult = MapUtil.<String, String>builder()
                    .put("appId", getWeixinPayConfig(val(paymentModel.getMode()),groupId).getAppid())
                    .put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000))
                    .put("nonceStr", WXPayUtil.generateNonceStr())
                    .put("package", StrUtil.format("prepay_id={}", result.get("prepay_id")))
                    .put("signType", "MD5")
                    .build();

            returnResult.put("paySign", WXPayUtil.generateSignature(returnResult, getWeixinPayConfig(val(paymentModel.getMode()),groupId).getSignKey()));
            if (result.containsKey("openid")) {
                returnResult.put("openid", result.get("openid"));
            }
            return returnResult;
        }
        log.error("签名错误：{},上传数据{}", result);
        throw new BizRuntimeException(OrderResultCode.payFail);
    }

    protected Map<String, String> baseParam(PaymentModel paymentModel, Map<String, Object> otherParam) {
        String orderOnlinePayTitle = orderService.getOrderOnlinePayTitle(paymentModel.getOrderId());
        if (orderOnlinePayTitle.getBytes(StandardCharsets.UTF_8).length > 127) {
            orderOnlinePayTitle = orderOnlinePayTitle.substring(0, 32);
        }

        return MapUtil.<String, String>builder()
                .put("body", orderOnlinePayTitle)
                .put("out_trade_no", paymentModel.getOrderSn())
                .put("total_fee", String.valueOf(paymentModel.getPaymentAmount()))
                .put("spbill_create_ip", otherParam.get("ip").toString())
                .put("notify_url", getWeixinPayConfig(val(paymentModel.getMode()),orderService.getById(paymentModel.getOrderId()).getGroupId()).getNotifyUrl())
                .build();
    }

    protected void _openProcess(PaymentModel paymentModel, Object val, Map<String, Object> otherParam) throws BizException {
        String payUserId = paymentModel.getPayUserId();
        if ((val == WeixinPayType.xcx) && StrUtil.isNotBlank(otherParam.get("code").toString()) && StrUtil.isBlank(otherParam.get("openid").toString())) {
            Map<String, Object> accessTokenMap = wxcxLoginService.getAccessToken(otherParam.get("code").toString(), DeviceType.wxcx);
            if (null != accessTokenMap.get("openid")) {
                String openid = accessTokenMap.get("openid").toString();
                String unionid = accessTokenMap.get("unionid").toString();
                if (StrUtil.isBlank(openid) || StrUtil.isBlank(unionid)) {
                    throw new BizException(UserResultCode.getUserInfoFail);
                }

                otherParam.put("openid", openid);
            }
        } else if (val == WeixinPayType.jsapi && StrUtil.isNotBlank(otherParam.get("code").toString()) && StrUtil.isBlank(otherParam.get("openid").toString())) {
            String appid = weixinPayConfig.getWeCatGzhAppid();
            String secret = weixinPayConfig.getWeCatGzhSecret();
            String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appid, secret, otherParam.get("code").toString());
            String resp = HttpUtil.get(url);
            log.error("获取openId{}", resp);
            JSONObject jsonObject = JSONObject.parseObject(resp);
            otherParam.put("openid", jsonObject.get("openid"));
        }


    }

    protected volatile WXPay wxPay;

    protected volatile WXPay wxXcxPay;

    protected WeixinPayType val(String val) {
        if (StrUtil.isNotBlank(val)) {
            return WeixinPayType.valueOf(val);
        }
        return WeixinPayType.unknown;
    }

    protected synchronized WeixinPayConfig getWeixinPayConfig(WeixinPayType weixinPayType, Long groupId) {
        WeixinPayConfig payConfig = new WeixinPayConfig();
        //BeanUtils.copyProperties(weixinPayConfig, payConfig);
        GroupModel group = groupService.getById(groupId);
        if (null != group) {
            payConfig.setAppid(group.getWeixinpayAppId());
            payConfig.setAppsecret(group.getWeixinpayAppSecret());
            payConfig.setMchid(group.getWeixinpayMchId());
            payConfig.setSignKey(group.getWeixinpaySignKey());
            payConfig.setNotifyUrl(group.getWeixinpayNotifyUrl());
            payConfig.setCert(group.getWeixinpayCertPath());
        }
        if (weixinPayType.compareTo(WeixinPayType.xcx) == 0) {
            payConfig.setAppid(thirdOauthConfig.getWeCatXcxAppid());
            payConfig.setAppsecret(thirdOauthConfig.getWeCatXcxSecret());
        } else if (weixinPayType.compareTo(WeixinPayType.jsapi) == 0) {
            if (group != null) {
                payConfig.setAppid(group.getGzhAppId());
                payConfig.setAppsecret(group.getGzhAppSecret());
            }

        }
        return payConfig;
    }

    protected WXPay getWxPay(WeixinPayType weixinPayType,Long groupId) {
        synchronized (WeiXinPayPlugins.class) {
            wxPay = getWxPay(getWeixinPayConfig(weixinPayType, groupId));
        }
        return wxPay;
    }

    protected WXPay getWxPay(WeixinPayConfig config) {
        return new WXPay(new WXPayConfig() {
            @Override
            public String getAppID() {
                return config.getAppid();
            }

            @Override
            public String getMchID() {
                return config.getMchid();
            }

            @Override
            public String getKey() {
                return config.getSignKey();
            }

            @Override
            public InputStream getCertStream() {
                try {
                    return new FileInputStream(config.getCert());
                } catch (FileNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public int getHttpConnectTimeoutMs() {
                return 10000;
            }

            @Override
            public int getHttpReadTimeoutMs() {
                return 10000;
            }
        });
    }
}
